package com.peng.rpc.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 反射扫描包路径
 * @author Wonderson
 * @email wpp.ds@foxmail.com
 * @date 2022/3/7 22:44
 */
@Slf4j
public class ReflectUtil {

    public static String getStackTrace() {
        StackTraceElement[] stackTraces = new Throwable().getStackTrace();
        return stackTraces[stackTraces.length - 1].getClassName();
    }

    public static Set<Class<?>> getClasses(String packageName) {
        String packagePath = packageName.replace(".", "/");
        Set<Class<?>> classes = new LinkedHashSet<>();
        boolean recursive = true;
        Enumeration<URL> urls = null;
        try {
            urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            if (urls == null) {
                throw new IllegalArgumentException("错误的包路径");
            }
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String protocol = url.getProtocol();
                //下面的代码的目标：1)获取形如 xxx.yyy.A 这样的全限定类名，2)加入集合classes中
                if ("file".equals(protocol)) {
                    String urlPath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddAllClassesInFileMode(packageName, urlPath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    JarFile jar;
                    jar = ((JarURLConnection)url.openConnection()).getJarFile();
                    Enumeration<JarEntry> jarEntries = jar.entries();
                    while (jarEntries.hasMoreElements()) {
                        //jar包之下，可能是类文件、其他文件、文件夹
                        JarEntry jarEntry = jarEntries.nextElement();
                        String jarEntryName = jarEntry.getName();
                        if (jarEntryName.startsWith("/")) {
                            jarEntryName = jarEntryName.substring(1);
                        }
                        if (jarEntryName.startsWith(packagePath)) {
                            //获取全部的包名
                            int idx = packageName.lastIndexOf("/");
                            if (idx != -1) {
                                packageName = packageName.substring(0, idx).replaceAll("/", "\\.");
                            }
                            if (idx != -1 && recursive) {
                                if (jarEntryName.endsWith(".class") && !jarEntry.isDirectory()) {
                                    //去除.class
                                    String className = jarEntryName.substring(0, jarEntryName.length() - 6);
                                    className = packageName + "." + className;
                                    Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                                    classes.add(clazz);
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            log.error("加载{}下的类出现错误：{}", packageName, e);
        }
        return classes;
    }

    private static void findAndAddAllClassesInFileMode(String packageName, String packagePath, boolean recursive, Set<Class<?>> classes) {
        File file = new File(packagePath);
        if (!file.exists() || !file.isDirectory()) {
            return;
        }
        //获取包路径下所有目录和文件
        File[] files = file.listFiles(f -> (recursive && f.isDirectory()) || f.getName().endsWith(".class"));
        for (File f : files) {
            if (f.isDirectory()) {
                //继续递归查找
                findAndAddAllClassesInFileMode(packageName + "." + f.getName(), f.getAbsolutePath(), recursive, classes);
            } else {
                String className = f.getName();
                //去掉.class后缀
                className = className.substring(0, className.length() - 6);
                try {
                    //使用loadClass()代替Class.forName()，可以避免调用类的static部分
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
                } catch (ClassNotFoundException e) {
                    log.error("不存在这个类:{}, {}", packageName + "." + className, e.getMessage());
                }
            }
        }
    }

}
